#include <graphics/Model.h>
#include <graphics/ModelFileFormat.h>
#include <graphics/MeshManager.h>
#include <graphics/SkeletonManager.h>
#include <animation/AnimationManager.h>
#include <system/Log.h>

namespace ne
{
    Model::Model(ResourceManager *pManager,
                 const uint64_t id,
                 const std::string &name,
                 const std::string &fileName,
                 const bool isManual,
                 ManualResourceLoader *pLoader)
        : Node(),
          Resource(pManager, id, name, fileName, isManual, pLoader),
          mSpeed(1.0f),
          mPausingAnimation(true),
          mNeedUpdateAABB(true)
    {
        mpSkeleton = SkeletonManager::GetSingleton().create(name + "_skeleton", name + "_skeleton", true);
        mpSkeleton->load();
    }

    Skin* Model::createSkin(const std::string &name,
                            const MeshPtr &pMesh)
    {
        if (!hasSkin(name))
        {
            Skin *pSkin = new Skin(name);
            pSkin->setMesh(pMesh);
            mSkinMap[name] = pSkin;
            return pSkin;
        }
        return 0;
    }

    Skin* Model::getSkin(const std::string &name) const
    {
        SkinMapConstIterator it = mSkinMap.find(name);
        if (it != mSkinMap.end())
        {
            return it->second;
        }
        return 0;
    }

    bool Model::hasSkin(const std::string &name) const
    {
        SkinMapConstIterator it = mSkinMap.find(name);
        if (it != mSkinMap.end())
        {
            return true;
        }
        return false;
    }

    size_t Model::getSkinCount() const
    {
        return mSkinMap.size();
    }

    bool Model::destroySkin(const std::string &name)
    {
        SkinMapIterator it = mSkinMap.find(name);
        if (it != mSkinMap.end())
        {
            delete it->second;
            mSkinMap.erase(it);
            return true;
        }
        return false;
    }

    void Model::destroyAllSkins()
    {
        for (SkinMapIterator it=mSkinMap.begin(); it!=mSkinMap.end(); ++it)
        {
            delete it->second;
        }
        mSkinMap.clear();
    }

    const SkinMap& Model::getSkinMap() const
    {
        return mSkinMap;
    }

    void Model::setSkeleton(const SkeletonPtr &pSkeleton)
    {
        stopAllAnimation();
        mpSkeleton->destroyAllJoint();
        if (pSkeleton.notNull())
        {
            mpSkeleton->copyFrom(*pSkeleton);
        }
        needUpdateAABB();
    }

    SkeletonPtr Model::getSkeleton() const
    {
        return mpSkeleton;
    }

    void Model::setAnimationGroup(const AnimationGroupPtr &pAnimationGroup)
    {
        stopAllAnimation();
        mpAnimationGroup = pAnimationGroup;
    }

    AnimationGroupPtr Model::getAnimationGroup() const
    {
        return mpAnimationGroup;
    }

    void Model::setSpeed(const real speed)
    {
        mSpeed = speed;
    }

    real Model::getSpeed() const
    {
        return mSpeed;
    }

    const AABB& Model::getAABB() const
    {
        _updateAABB();
        return mAABB;
    }

    void Model::playAnimation(const std::string &name,
                              const uint32_t layer,
                              const bool restart,
                              const bool loop,
                              const real transTime,
                              const real weight,
                              const real scale,
                              const uint32_t userData)
    {
        if (mpAnimationGroup.isNull() || mpSkeleton.isNull())
        {
            return;
        }

        AnimationRTDMapConstIterator it = mCurrentAnimationMap.find(layer);
        if (it == mCurrentAnimationMap.end())
        {
            AnimationPtr pAnimation(mpAnimationGroup->getByName(name));
            if (pAnimation.notNull())
            {
                AnimationRTD *pRTD = new AnimationRTD();
                pRTD->mLayer = layer;
                pRTD->mpAnimation = pAnimation;
                pRTD->mRestart = restart;
                pRTD->mLoop = loop;
                pRTD->mTime = 0.0f;
                pRTD->mTransTime = transTime;
                pRTD->mWeight = weight;
                pRTD->mScale = scale;
                pRTD->mUserData = userData;
                mCurrentAnimationMap[layer] = pRTD;
            }
        }
        else
        {
            AnimationPtr pAnimation(mpAnimationGroup->getByName(name));
            if (pAnimation.notNull())
            {
                AnimationRTD *pRTD = it->second;
                if (pRTD->mpAnimation->getName() != name || restart)
                {
                    pRTD->mLayer = layer;
                    pRTD->mpAnimation = pAnimation;
                    pRTD->mRestart = restart;
                    pRTD->mLoop = loop;
                    pRTD->mTime = 0.0f;
                    pRTD->mTransTime = transTime;
                    pRTD->mWeight = weight;
                    pRTD->mScale = scale;
                    pRTD->mUserData = userData;
                }
                else
                {
                    pRTD->mRestart = restart;
                    pRTD->mLoop = loop;
                    pRTD->mTransTime = transTime;
                    pRTD->mWeight = weight;
                    pRTD->mScale = scale;
                    pRTD->mUserData = userData;
                }
            }
        }

        // Remove the request animation
        AnimationRTDMapIterator irequest = mRequestAnimationMap.find(layer);
        if (irequest != mRequestAnimationMap.end())
        {
            delete irequest->second;
            mRequestAnimationMap.erase(irequest);
        }

        mPausingAnimation = false;
    }

    void Model::requestAnimation(const std::string &name,
                                 const uint32_t layer,
                                 const bool loop,
                                 const real transTime,
                                 const real weight,
                                 const real scale,
                                 const uint32_t userData)
    {
        if (mpAnimationGroup.isNull() || mpSkeleton.isNull())
        {
            return;
        }

        AnimationRTDMapConstIterator it = mRequestAnimationMap.find(layer);
        if (it == mRequestAnimationMap.end())
        {
            AnimationPtr pAnimation(mpAnimationGroup->getByName(name));
            if (pAnimation.notNull())
            {
                AnimationRTD *pRTD = new AnimationRTD();
                pRTD->mLayer = layer;
                pRTD->mpAnimation = pAnimation;
                pRTD->mRestart = true;
                pRTD->mLoop = loop;
                pRTD->mTime = 0.0f;
                pRTD->mTransTime = transTime;
                pRTD->mWeight = weight;
                pRTD->mScale = scale;
                pRTD->mUserData = userData;
                mRequestAnimationMap[layer] = pRTD;
            }
        }
        else
        {
            AnimationPtr pAnimation(mpAnimationGroup->getByName(name));
            if (pAnimation.notNull())
            {
                AnimationRTD *pRTD = it->second;
                pRTD->mLayer = layer;
                pRTD->mpAnimation = pAnimation;
                pRTD->mRestart = true;
                pRTD->mLoop = loop;
                pRTD->mTime = 0.0f;
                pRTD->mTransTime = transTime;
                pRTD->mWeight = weight;
                pRTD->mScale = scale;
                pRTD->mUserData = userData;
            }
        }

        mPausingAnimation = false;
    }

    void Model::pauseAnimation()
    {
        mPausingAnimation = true;
    }

    void Model::continueAnimation()
    {
        mPausingAnimation = false;
    }

    void Model::stopAnimation(const uint32_t layer)
    {
        AnimationRTDMapIterator it = mCurrentAnimationMap.find(layer);
        if (it != mCurrentAnimationMap.end())
        {
            delete it->second;
            mCurrentAnimationMap.erase(it);
        }

        it = mRequestAnimationMap.find(layer);
        if (it != mRequestAnimationMap.end())
        {
            delete it->second;
            mRequestAnimationMap.erase(it);
        }
    }

    void Model::stopAllAnimation()
    {
        for (AnimationRTDMapIterator it=mCurrentAnimationMap.begin(); it!=mCurrentAnimationMap.end(); ++it)
        {
            delete it->second;
        }
        mCurrentAnimationMap.clear();

        for (AnimationRTDMapIterator it=mRequestAnimationMap.begin(); it!=mRequestAnimationMap.end(); ++it)
        {
            delete it->second;
        }
        mRequestAnimationMap.clear();

        mPausingAnimation = true;
    }

    const Model::AnimationRTD* Model::getCurrentAnimationRTD(const uint32_t layer) const
    {
        AnimationRTDMapConstIterator it = mCurrentAnimationMap.find(layer);
        if (it != mCurrentAnimationMap.end())
        {
            return it->second;
        }
        return 0;
    }

    AnimationPtr Model::getCurrentAnimation(const uint32_t layer) const
    {
        AnimationRTDMapConstIterator it = mCurrentAnimationMap.find(layer);
        if (it != mCurrentAnimationMap.end())
        {
            return it->second->mpAnimation;
        }
        return AnimationPtr();
    }

    uint32_t Model::getCurrentAnimationUserData(const uint32_t layer) const
    {
        AnimationRTDMapConstIterator it = mCurrentAnimationMap.find(layer);
        if (it != mCurrentAnimationMap.end())
        {
            return it->second->mUserData;
        }
        return 0;
    }

    bool Model::isPlayingAnimation() const
    {
        return (!mPausingAnimation && !mCurrentAnimationMap.empty());
    }

    bool Model::isPausingAnimation() const
    {
        return mPausingAnimation;
    }

    bool Model::tick(const real interval)
    {
        if (!mPausingAnimation && mpSkeleton.notNull())
        {
            mpSkeleton->reset();

            real dT = interval * mSpeed;
            std::list<uint32_t> currentLayersToRemove;
            std::list<uint32_t> requestLayersToPlay;
            AnimationRTDMapIterator icurrent, irequest;

            // Tick current animations
            for (icurrent=mCurrentAnimationMap.begin(); icurrent!=mCurrentAnimationMap.end(); ++icurrent)
            {
                AnimationRTD *pRTD = icurrent->second;
                pRTD->mTime += dT;

                if (pRTD->mLoop)
                {
                    pRTD->mTime = std::fmod(pRTD->mTime, pRTD->mpAnimation->getLength());
                }

                real length = pRTD->mpAnimation->getLength();
                if (pRTD->mTime > length)
                {
                    // Mark the animation for remove
                    currentLayersToRemove.push_back(pRTD->mLayer);
                    // Mark the animation for play in next tick
                    requestLayersToPlay.push_back(pRTD->mLayer);
                    irequest = mRequestAnimationMap.find(pRTD->mLayer);
                    if (irequest != mRequestAnimationMap.end())
                    {
                        // Tick the left time
                        irequest->second->mTime = pRTD->mTime - length;
                    }
                    pRTD->mTime = length;
                }

                // TODO: Blend TransTime need to fix
                real weight = pRTD->mWeight;
                if (pRTD->mTransTime > pRTD->mTime)
                {
                    weight *= pRTD->mTime / pRTD->mTransTime;
                }
                pRTD->mpAnimation->applyToSkeleton(mpSkeleton.ptr(), pRTD->mTime, weight, pRTD->mScale);
            }
            mpSkeleton->update();
            needUpdateAABB();

            // Tick request animations
            for (irequest=mRequestAnimationMap.begin(); irequest!=mRequestAnimationMap.end(); ++irequest)
            {
                AnimationRTD *pRTD = irequest->second;
                icurrent = mCurrentAnimationMap.find(pRTD->mLayer);
                if (icurrent == mCurrentAnimationMap.end())
                {
                    // Mark the animation for play in next tick
                    pRTD->mTime = dT;
                    requestLayersToPlay.push_back(pRTD->mLayer);
                }
            }

            // Now remove the animations we marked for death
            std::list<uint32_t>::iterator i, ie;
            ie = currentLayersToRemove.end();
            for (i=currentLayersToRemove.begin(); i!=ie; ++i)
            {
                icurrent = mCurrentAnimationMap.find(*i);
                if (icurrent != mCurrentAnimationMap.end())
                {
                    delete icurrent->second;
                    mCurrentAnimationMap.erase(icurrent);
                }
            }

            // Now change the request animations we marked to current animations
            // To play in next tick
            ie = requestLayersToPlay.end();
            for (i=requestLayersToPlay.begin(); i!=ie; ++i)
            {
                irequest = mRequestAnimationMap.find(*i);
                if (irequest != mRequestAnimationMap.end())
                {
                    mCurrentAnimationMap[*i] = irequest->second;
                    mRequestAnimationMap.erase(irequest);
                }
            }

            return true;
        }
        return false;
    }

    // TODO: Need to fix
    void Model::update()
    {
        Node::update();
        updateAABB();
    }

    void Model::needUpdate(const bool forceParentUpdate)
    {
        Node::needUpdate(forceParentUpdate);
        needUpdateAABB();
    }

    void Model::needUpdateAABB()
    {
        mNeedUpdateAABB = true;
    }

    void Model::updateAABB()
    {
        _updateAABB();
    }

    Model::~Model()
    {
        unload();

        if (SkeletonManager::GetSingletonPtr())
        {
            SkeletonManager::GetSingleton().removeByPtr(mpSkeleton);
        }
    }

    bool Model::loadImplement()
    {
        FILE *file = openFile("rb");
        if (!file)
        {
            Log::Out(LL_WARNING,
                "Open Model (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        ModelHeader modelHeader;
        if (!fread(&modelHeader, sizeof(ModelHeader), 1, file))
        {
            closeFile(file);
            Log::Out(LL_WARNING,
                "Load Model (%s) from file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        if (modelHeader.mMagicNum != MODEL_FILE_MAGIC_NUMBER ||
            modelHeader.mVersion != MODEL_FILE_VERSION)
        {
            closeFile(file);
            Log::Out(LL_WARNING,
                "Load Model (%s) from file (%s) with wrong Magic or Version", getName().c_str(), getFileName().c_str());
        }

        // Create Skeleton
        std::string skeletonName = modelHeader.mSkeletonName;
        if (!skeletonName.empty())
        {
            SkeletonPtr pSkeleton(SkeletonManager::GetSingleton().createOrRetrieve(skeletonName, skeletonName).first);
            if (pSkeleton.notNull())
            {
                if (!pSkeleton->isLoaded())
                {
                    pSkeleton->load();
                }
                mpSkeleton->copyFrom(*pSkeleton);
            }
        }

        // Create AnimationGroup
        std::string animationGroupName = modelHeader.mAnimationGroupName;
        if (!animationGroupName.empty())
        {
            mpAnimationGroup = AnimationManager::GetSingleton().createOrRetrieve(animationGroupName, animationGroupName).first;
            if (mpAnimationGroup.notNull())
            {
                if (!mpAnimationGroup->isLoaded())
                {
                    mpAnimationGroup->load();
                }
            }
        }

        // Seek to SkinHeader
        fseek(file, modelHeader.mSkinOffset, SEEK_SET);

        // Create Skins
        SkinHeader skinHeaders[modelHeader.mSkinCount];
        if (!fread(skinHeaders, sizeof(SkinHeader), modelHeader.mSkinCount, file))
        {
            closeFile(file);
            Log::Out(LL_WARNING,
                "Load Skin for Model (%s) from file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        closeFile(file);
        for (uint32_t i=0; i<modelHeader.mSkinCount; ++i)
        {
            MeshPtr pMesh(MeshManager::GetSingleton().createOrRetrieve(skinHeaders[i].mMeshName, skinHeaders[i].mMeshName).first);
            if (pMesh.notNull())
            {
                if (!pMesh->isLoaded())
                {
                    pMesh->load();
                }
                Skin *pSkin = createSkin(skinHeaders[i].mName, pMesh);
                if (!pSkin)
                {
                    Log::Out(LL_WARNING,
                        "Create Skin for Model (%s) from file (%s) failed", getName().c_str(), getFileName().c_str());
                }
            }
        }

        needUpdateAABB();

        return true;
    }

    bool Model::unloadImplement()
    {
        stopAllAnimation();
        destroyAllSkins();
        removeAllChildren();
        mpSkeleton->destroyAllJoint();
        mpAnimationGroup.setNull();
        return true;
    }

    bool Model::downloadImplement()
    {
        FILE *file = openFile("wb");
        if (!file)
        {
            Log::Out(LL_WARNING,
                "Create Model (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        ModelHeader modelHeader;
        memset(&modelHeader, 0, sizeof(ModelHeader));

        modelHeader.mMagicNum = MODEL_FILE_MAGIC_NUMBER;
        modelHeader.mVersion = MODEL_FILE_VERSION;
        strncpy(modelHeader.mName, getName().c_str(),
                MAX_STRING_LENGTH_IN_MODEL_FILE);

        if (mpSkeleton.notNull())
        {
            strncpy(modelHeader.mSkeletonName, mpSkeleton->getFileName().c_str(),
                    MAX_STRING_LENGTH_IN_MODEL_FILE);
        }

        if (mpAnimationGroup.notNull())
        {
            strncpy(modelHeader.mAnimationGroupName, mpAnimationGroup->getFileName().c_str(),
                    MAX_STRING_LENGTH_IN_MODEL_FILE);
        }

        modelHeader.mSkinCount = getSkinCount();
        fwrite(&modelHeader, sizeof(ModelHeader), 1, file);
        modelHeader.mSkinOffset = ftell(file);
        fseek(file, 0, SEEK_SET);
        fwrite(&modelHeader, sizeof(ModelHeader), 1, file);

        SkinHeader skinHeaders[modelHeader.mSkinCount];
        memset(skinHeaders, 0, sizeof(SkinHeader) * modelHeader.mSkinCount);

        SkinMapConstIterator it = mSkinMap.begin();
        for (uint32_t i=0; i<modelHeader.mSkinCount; ++i)
        {
            Skin *pSkin = it->second;
            if (pSkin)
            {
                strncpy(skinHeaders[i].mName, pSkin->getObjectName().c_str(),
                        MAX_STRING_LENGTH_IN_MODEL_FILE);
                MeshPtr pMesh(pSkin->getMesh());
                if (pMesh.notNull())
                {
                    strncpy(skinHeaders[i].mMeshName, pMesh->getName().c_str(),
                            MAX_STRING_LENGTH_IN_MODEL_FILE);
                }
            }
            ++it;
        }

        fseek(file, modelHeader.mSkinOffset, SEEK_SET);
        fwrite(skinHeaders, sizeof(SkinHeader), modelHeader.mSkinCount, file);
        closeFile(file);

        return true;
    }

    size_t Model::_calculateSize() const
    {
        size_t size = 0;
        if (mpSkeleton.notNull())
        {
            size += mpSkeleton->getSize();
        }

        if (mpAnimationGroup.notNull())
        {
            size += mpAnimationGroup->getSize();
        }

        for (SkinMapConstIterator it=mSkinMap.begin(); it!=mSkinMap.end(); ++it)
        {
            Skin *pSkin = it->second;
            if (pSkin)
            {
                MeshPtr pMesh(pSkin->getMesh());
                if (pMesh.notNull())
                {
                    size += pMesh->getSize();
                }
            }
        }

        return size;
    }

    void Model::_updateAABB() const
    {
        if (mpSkeleton.notNull() && mNeedUpdateAABB)
        {
            mAABB.setExtent(AABB_EXTENT_NULL);
            
            const Matrix4 &modelMatrix = getTransformMatrix();
            const JointVector &joints = mpSkeleton->getJointVector();
            for (JointVectorConstIterator it=joints.begin(); it!=joints.end(); ++it)
            {
                ne::Joint *pJoint = *it;
                mAABB.merge(pJoint->getPosition(TS_WORLD) * modelMatrix);
            }
            mNeedUpdateAABB = false;
        }
    }
}

